﻿Imports System.IO
Imports System.IO.Path
Imports System.IO.Compression
Imports PowerSong.SongDatabase.Legacy.Items

Namespace ExportImport

    ''' <summary>
    ''' Represents the import and export functionality of PowerSong databases.
    ''' </summary>
    Public Class PrimaryFacility

        Private FDatabase As Database = Nothing

        ''' <summary>
        ''' Initializes a new instance of the <see cref="PrimaryFacility" /> class.
        ''' </summary>
        ''' <param name="sourceDatabase">The source database to use for the export/import.</param>
        Public Sub New(ByVal sourceDatabase As Database)
            FDatabase = sourceDatabase
        End Sub

        ''' <summary>
        ''' Exports the entire song database into a single file.
        ''' </summary>
        ''' <param name="fileName">The name of the target archive file.</param>
        Public Sub Export(ByVal fileName As String, _
                          ByVal exportSongs As Boolean, _
                          ByVal includeCategories As Boolean, _
                          ByVal exportPlugins As Boolean, _
                          ByVal exportStyles As Boolean)

            Dim DS As Data = FDatabase.Data

            ' Create the output stream
            Using File As New BinaryWriter(New MemoryStream)

                ' Write all files in this folder to the file
                If exportSongs Then
                    For Each SourceFilename As String In Directory.GetFiles(FDatabase.Location, "*.song")
                        WriteFileToStream(File, SourceFilename, GetFileName(SourceFilename))
                    Next
                End If

                If includeCategories Then

                    ' Create a dataset for exporting
                    Dim CategoriesDataset As New Data
                    For Each Row As Data.CategoriesRow In DS.Categories
                        CategoriesDataset.Categories.ImportRow(Row)
                    Next
                    For Each Row As Data.SongsRow In DS.Songs
                        CategoriesDataset.Songs.ImportRow(Row)
                    Next
                    For Each Row As Data.SongCategoriesRow In DS.SongCategories
                        CategoriesDataset.SongCategories.ImportRow(Row)
                    Next

                    ' Write the dataset to the archive
                    Using Writer As New StringWriter
                        CategoriesDataset.WriteXml(Writer)
                        WriteTextToStream(File, Writer.ToString, "SongCategories.xml")
                    End Using

                End If

                If exportPlugins Then
                    For Each SourceFilename As String In Directory.GetFiles(FDatabase.Location, "*.plugin")
                        WriteFileToStream(File, SourceFilename, GetFileName(SourceFilename))
                    Next
                End If

                If exportStyles Then

                    ' Create a dataset for exporting
                    Dim StylesDataset As New Data
                    For Each Row As Data.StylesRow In DS.Styles
                        StylesDataset.Styles.ImportRow(Row)
                    Next
                    For Each Row As Data.ProjectletsRow In DS.Projectlets
                        StylesDataset.Projectlets.ImportRow(Row)
                    Next

                    ' Write the dataset to the archive
                    Using Writer As New StringWriter
                        StylesDataset.WriteXml(Writer)
                        WriteTextToStream(File, Writer.ToString, "Styles.xml")
                    End Using

                End If

                ' Get the data as a sequence of bytes
                Dim Data(File.BaseStream.Length) As Byte
                File.BaseStream.Position = 0
                File.BaseStream.Read(Data, 0, File.BaseStream.Length)

                ' Write to a compressed stream
                Using CompressedFile As New GZipStream(New FileStream(fileName, FileMode.Create), CompressionMode.Compress)
                    CompressedFile.Write(Data, 0, File.BaseStream.Length)
                End Using

            End Using

        End Sub

        Public Function Import(ByVal fileStream As Stream, _
                               ByVal overwriteItems As Boolean) As List(Of String)

            ' Start a transaction
            Dim DS As Data = FDatabase.Data
            Dim Messages As New List(Of String)
            FDatabase.StartTransaction()
            Try

                ' Decompress all of the data in the given file into a memory stream
                Dim ZipStream As New GZipStream(fileStream, CompressionMode.Decompress)
                Dim MemoryStream As New MemoryStream
                Dim Buffer(4096) As Byte
                Dim BytesRead As Integer = ZipStream.Read(Buffer, 0, 4096)
                While BytesRead > 0
                    MemoryStream.Write(Buffer, 0, BytesRead)
                    BytesRead = ZipStream.Read(Buffer, 0, 4096)
                End While

                ' Start reading from the beginning of the memory stream
                MemoryStream.Position = 0
                Dim File As New BinaryReader(MemoryStream)

                ' Read each file in the stream
                While File.BaseStream.Position < File.BaseStream.Length
                    Dim NextFileName As String = File.ReadString
                    Dim FileLength As Integer = File.ReadInt32
                    Dim FileContents As Byte() = File.ReadBytes(FileLength)
                    Dim OutputFileName As String = FDatabase.Location + "\" + NextFileName

                    ' Work with the current file in the stream
                    Select Case GetExtension(NextFileName).ToUpper

                        Case ".SONG"
                            ImportSong(DS, FileContents, OutputFileName, overwriteItems, Messages)

                        Case ".PLUGIN"
                            Messages.Add("Did not import plugin '" + NextFileName + "' because plugins are no longer supported.")

                        Case ".XML"

                            Select Case NextFileName.ToUpper

                                Case "STYLES.XML"
                                    ImportStyles(DS, FileContents, overwriteItems, Messages)

                                Case "SONGCATEGORIES.XML"
                                    Messages.Add("Song Categories have been included in the selected file but they cannot yet be imported. It is likely that a future version of PowerSong will allow Song Categories to be imported from the specified file.")

                                Case Else
                                    Messages.Add("Skipped over unknown file: '" + NextFileName + "'.")

                            End Select

                        Case Else
                            Messages.Add("Skipped over unknown file: '" + NextFileName + "'.")

                    End Select

                End While

            Finally
                FDatabase.EndTransaction()
            End Try

            ' Perform some checks on the database - this assigns a category to uncategorised songs
            FDatabase.Save()
            DatabaseUpgrade.CheckSongCategories(FDatabase)

            Return Messages

        End Function

        Private Sub ImportSong(ByVal dataSet As Data, _
                               ByVal fileContents As Byte(), _
                               ByVal destinationFileName As String, _
                               ByVal overwriteExisting As Boolean, _
                               ByVal messages As List(Of String))

            ' Determine whether or not to import/overwrite the song
            Dim ImportThisSong As Boolean = True
            If IO.File.Exists(destinationFileName) Then
                If Not overwriteExisting Then
                    messages.Add("The song '" + destinationFileName + "' already exists.")
                    ImportThisSong = False
                End If
            End If

            If ImportThisSong = True Then

                Try

                    ' Write the song
                    Dim OutputFile As New BinaryWriter(New FileStream(destinationFileName, FileMode.Create))
                    OutputFile.Write(fileContents)
                    OutputFile.Close()

                    ' Add to the database if necessary
                    Dim Song As SongItem = SongItem.Load(destinationFileName)
                    Dim ExistingSong As Data.SongsRow = dataSet.Songs.FindBySongID(Song.SongID)
                    If ExistingSong IsNot Nothing Then dataSet.Songs.RemoveSongsRow(ExistingSong)
                    Dim SongRow As Data.SongsRow = dataSet.Songs.AddSongsRow(Song.SongID, Song.Title, Nothing)

                    ' Display information about the song
                    messages.Add("Added song: '" + Song.Title + "'.")

                Catch ex As Exception
                    messages.Add("Song import error: " + ex.Message)
                End Try

            End If

        End Sub

        Private Sub ImportStyles(ByVal dataSet As Data, _
                                 ByVal fileContents As Byte(), _
                                 ByVal overwriteExisting As Boolean, _
                                 ByVal messages As List(Of String))

            Try

                ' Get the styles
                Dim StyleData As New Data
                Dim Reader As New Xml.XmlTextReader(New MemoryStream(fileContents))
                StyleData.ReadXml(Reader)
                Reader.Close()

                ' Import the styles
                For Each Row As Data.StylesRow In StyleData.Styles

                    Try
                        Dim ExistingStyle As Data.StylesRow() = dataSet.Styles.Select("Name='" + Row.Name + "'")
                        If ExistingStyle.Length = 0 Then

                            ' Add the new style
                            dataSet.Styles.ImportRow(Row)
                            For Each SubRow As Data.ProjectletsRow In StyleData.Projectlets.Select("StyleID='" + Row.StyleID.ToString + "'")
                                dataSet.Projectlets.ImportRow(SubRow)
                            Next
                            messages.Add("Added style '" + Row.Name + "'.")

                        ElseIf overwriteExisting Then

                            ' Overwrite existing style
                            dataSet.Styles.RemoveStylesRow(ExistingStyle(0))
                            dataSet.Styles.ImportRow(Row)
                            For Each SubRow As Data.ProjectletsRow In StyleData.Projectlets.Select("StyleID='" + Row.StyleID.ToString + "'")
                                dataSet.Projectlets.ImportRow(SubRow)
                            Next
                            messages.Add("Replaced style '" + Row.Name + "'.")

                        Else
                            messages.Add("The style '" + Row.Name + "' already exists, therefore it has been ignored.")
                        End If

                    Catch ex As Exception
                        messages.Add("Cannot import style '" + Row.Name + "' because it is incompatible.")
                    End Try

                Next

            Catch ex As Exception
                messages.Add("The styles file contained in the archive is invalid.")
            End Try

        End Sub

        Public Function Import(ByVal fileName As String, _
                               ByVal overwriteExistingSongs As Boolean) As List(Of String)

            Dim Stream As New FileStream(fileName, FileMode.Open)
            Dim Result As New List(Of String)

            Try
                Result = Import(Stream, overwriteExistingSongs)
            Finally
                Stream.Close()
            End Try

            Return Result

        End Function

        Private Sub WriteFileToStream(ByVal stream As BinaryWriter, _
                                      ByVal fileName As String, _
                                      ByVal fileNameInStream As String)

            stream.Write(fileNameInStream)
            Dim File As New BinaryReader(New FileStream(fileName, FileMode.Open))
            Dim FileLength As Integer = File.BaseStream.Length
            Dim Contents As Byte() = File.ReadBytes(FileLength)
            stream.Write(FileLength)
            stream.Write(Contents)
            File.Close()

        End Sub

        Private Sub WriteTextToStream(ByVal stream As BinaryWriter, _
                                      ByVal text As String, _
                                      ByVal fileNameInStream As String)

            stream.Write(fileNameInStream)
            Dim Data As Byte() = System.Text.Encoding.Default.GetBytes(text)
            stream.Write(Data.Length)
            stream.Write(Data)

        End Sub

        Private Sub AddExistingPlugin(ByVal destinationFileName As String, ByVal fileContents As Byte())

            ' Check extension
            If GetExtension(destinationFileName).ToUpper <> ".PLUGIN" Then
                Throw New ApplicationException("Plugin must have an extension of .PLUGIN.")
            End If

            If File.Exists(destinationFileName) Then Throw New ApplicationException("A plugin with the same file name already exists in the database.")
            Dim OutputFile As New FileStream(destinationFileName, FileMode.Create)
            OutputFile.Write(fileContents, 0, fileContents.Length)
            OutputFile.Close()

        End Sub

    End Class

End Namespace